﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Box2D.XNA;

//This will be the main class in which all objects will get loaded
//and get updated and drawn

namespace Imagine
{
    //The idea of each singleton class to be prefixed with "The" is quite interesting right.
    //First saw it in the open source Angel2D engine.
    public class TheWorld
    {
        #region Private_constructor
        TheWorld()
        {}
        static TheWorld instance;
        #endregion
        
        public List<RenderLayer> Renderlayers;
        
        #region Private_Parts
        Dictionary<int, List<GameObject>> m_renderMap;
        PhysicsWorld                      m_physicsWorld;
        int                               m_maxRenderLayers;
        #endregion

        #region Properties
        public World PhysicsWorld
        {
            get { return m_physicsWorld.Box2DWorld; }
        }

        public Dictionary<int, List<GameObject>> RenderMap
        {
            get { return m_renderMap;   }
        }
        public int MaxRenderLayers 
        {
            get { return m_maxRenderLayers; }
        }
        #endregion

        public void Initialize(GraphicsDeviceManager graphicsDeviceManager, int MaxRenderLayers, Vector2 gravity, bool sleep /*Sleep Inactive objects*/)
        {
            Renderlayers = new List<RenderLayer>();
            m_renderMap = new Dictionary<int, List<GameObject>>(); 
            m_maxRenderLayers = MaxRenderLayers;
            CreateRenderLayers( graphicsDeviceManager.GraphicsDevice, m_maxRenderLayers );
            //Init The Box2d World;  
            m_physicsWorld = new PhysicsWorld(gravity, sleep);
            m_physicsWorld.DrawDebugData = false;
            DebugDraw._device = Engine.Instance().GraphicsDeviceManager.GraphicsDevice;
            //Set All DebugDrawFlags
            m_physicsWorld.Box2DWorld.DebugDraw.Flags = DebugDrawFlags.Shape;
        }

        public void Update(GameTime time)
        {
            //Step The Box2D world
            m_physicsWorld.Step(time);

            //Call Each objects Update
            for(int layer = 0; layer < m_maxRenderLayers; ++layer)
            {
                //Setup layer's Transformation, to be applied on all child objects
                Renderlayers[layer].SetUpTransformation();

                //get the list of objects in the Current layer
                List<GameObject> objectList;
                m_renderMap.TryGetValue(layer, out objectList);

                int actorCount = objectList.Count;
                //Call Each Objects Update
                for(int gameObject = 0; gameObject < actorCount; ++gameObject)
                    objectList[gameObject].Update(time);
            }
        }      

        public void Draw(GameTime time)
        {
            //TODO : to be done later after i introduce effects
            m_physicsWorld.Draw(time);

            //get the SpriteBatch, i Know its bad, increasing Coupling :(
            SpriteBatch _spriteBatch = Engine.Instance().SpriteBatch;

            //Get the camera and setup the transformation
            Camera camera = Engine.Instance().Camera;
            
            //Just call Each Obbject's Draw
            for(int layer = 0; layer < m_maxRenderLayers; ++layer)
            {
                //get the list of objects in the Current layer
                List<GameObject> objectList;
                m_renderMap.TryGetValue(layer, out objectList);
                 
                int objectCount = objectList.Count;
                if(objectCount > 0)    //If There's objects to be rendered, then do your work
                {
                    //Set Paralax factor
                    camera.ParallaxFactor = Renderlayers[layer].ParallaxFactor;
                    
                    //Calculate the tranformation, here if layer specific transformation needed
                    Matrix finalTransformation = Renderlayers[layer].Transformation * camera.GetTransformation();
                    //Setup layer's rendering properties, to be applied on all child objects
                    Renderlayers[layer].BeginRendering(_spriteBatch, finalTransformation);

                    //Call Each Object's Draw
                    for (int gameObject = 0; gameObject < objectCount; ++gameObject)
                        objectList[gameObject].Draw(time);

                    //Now end The Drawing
                    Renderlayers[layer].EndRendering(_spriteBatch);
                }
            }
        }

        static public TheWorld Instance()
        {
            if( instance == null )
                instance = new TheWorld();
            return instance;
        }


        public void CreateRenderLayers(GraphicsDevice _device, int MaxRenderLayers) /*Checks how many total layers are there and then creates them with default Values */
        {
            for(int i = 0; i < MaxRenderLayers ; ++i)
            {
                Renderlayers.Add( new RenderLayer(_device) );
                //Insert an empty List of objects into RenderMap
                m_renderMap.Add(i, new List<GameObject>() );
            }
        }

        public void Add(GameObject object_, int layer_)
        {
            //I am doing no house Keeping here, at least for now;
            //Tell the Object which Layer's its going into
            List<GameObject> tempList;
            object_.RenderLayer = layer_;    
            m_renderMap.TryGetValue(layer_, out tempList);
            tempList.Add(object_);
        }

        public void Remove( GameObject object_ )
        {
             m_renderMap[object_.RenderLayer].Remove( object_ );
        }

        public int GetIndex( GameObject object_, int renderLayer_)
        {
            return m_renderMap[renderLayer_].IndexOf(object_);
        }
        
        public GameObject Get( int renderLayer_, int index_ )
        {
            return m_renderMap[renderLayer_][index_];
        }
         
        public void RemoveLayer( int layer_ )
        {
            Renderlayers.RemoveAt( layer_ );
            --m_maxRenderLayers;
        }

        public void AddLayer()
        {
            Renderlayers.Add( new RenderLayer(Engine.Instance().GraphicsDeviceManager.GraphicsDevice) );
            ++m_maxRenderLayers;
        }

        public void ChangeObjectLayer( GameObject gObj_, int newLayer_)
        {
            //First remove from Previous Layer
            m_renderMap[gObj_.RenderLayer].Remove( gObj_ );
            //Now Assign to new Layer
            gObj_.RenderLayer = newLayer_;
            m_renderMap[newLayer_].Add(gObj_);
        }
    }
}
